//
// PropsManager
//
// Copyright (C) jextra.net.
//
//  This file is part of the JavaProps Library.
//
//  The JavaProps Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The JavaProps Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the JavaProps Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.crack.props;

import java.io.*;
import java.util.*;

/**
 * The PropsManager is the basic functionality for the Props class. It is a singleton that is usually accessed via the
 * {@link CrackProps} class with static methods.
 */
public class CrackPropsManager
{
    // ============================================================
    // Fields
    // ============================================================

    /**
     * "Runtime" properties are properties that are added via the PropsManager method {@link #setString(String, String)}
     * . These are always first and override any subsequent property sources.
     */
    public static final String PROPSOURCE_RUNTIME = "<RUNTIME>";

    /**
     * "System" properties are collected via the <code>System.getProperties()</code>. These are typically properties set
     * as -D values on the javaVM command-line.
     */
    public static final String PROPSOURCE_SYSTEM = "<SYSTEM>";

    // Singleton instance of the factory. Get to this via static getInstance method.
    private static CrackPropsManager _instance;

    private LinkedHashMap<String, Properties> propertySources;
    private Properties runtimeProperties;
    private String[] standardFilenames =
    { "crack.properties", "build.properties" };

    // ============================================================
    // Constructors
    // ============================================================

    protected CrackPropsManager()
    {
        propertySources = new LinkedHashMap<String, Properties>();

        //
        // Add "runtime" properties as the first Properties file.
        //
        runtimeProperties = new Properties();
        addProperties( PROPSOURCE_RUNTIME, runtimeProperties );

        //
        // Add System properties (command-line) as the second Properties file in list.
        //
        addProperties( PROPSOURCE_SYSTEM, System.getProperties() );

        //
        // Add standard property files next.
        //
        for ( String filename : standardFilenames )
        {
            try
            {
                findAndLoadPropertyFile( filename );
            }
            catch ( Exception ex )
            {
                throw new CrackPropsException( ex );
            }
        }
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    /**
     * Get PropsManager singleton.
     */
    public static CrackPropsManager getInstance()
    {
        try
        {
            if ( _instance == null )
            {
                _instance = new CrackPropsManager();
            }
            return _instance;
        }
        catch ( Exception ex )
        {
            throw new CrackPropsException( ex );
        }
    }

    public void addProperties( String name, Properties properties )
    {
        if ( properties == null )
        {
            return;
        }

        propertySources.put( name, properties );
    }

    /**
     * Allows loading of custom properties files after the default files are loaded.
     */
    public void loadPropertiesFile( String filename )
    {
        try
        {
            findAndLoadPropertyFile( filename );
        }
        catch ( Exception ex )
        {
            throw new CrackPropsException( ex );
        }
    }

    public String getString( String key )
    {
        return getString( key, null );
    }

    public String getString( String key, String defaultValue )
    {
        String val = null;
        for ( String propName : propertySources.keySet() )
        {
            Properties props = propertySources.get( propName );
            val = props.getProperty( key );
            if ( val != null )
            {
                break;
            }
        }
        if ( val != null )
        {
            return val;
        }
        else
        {
            return defaultValue;
        }
    }

    public Integer getInteger( String key )
    {
        return getInteger( key, null );
    }

    public Integer getInteger( String key, Integer defaultValue )
    {
        String defaultString = null;
        String returnString = null;
        if ( defaultValue != null )
        {
            defaultString = defaultValue.toString();
        }
        returnString = getString( key, defaultString );
        if ( returnString != null )
        {
            return new Integer( returnString );
        }
        return null;
    }

    public Long getLong( String key )
    {
        return getLong( key, null );
    }

    public Long getLong( String key, Long defaultValue )
    {
        String defaultString = null;
        String returnString = null;
        if ( defaultValue != null )
        {
            defaultString = defaultValue.toString();
        }
        returnString = getString( key, defaultString );
        if ( returnString != null )
        {
            return new Long( returnString );
        }
        return null;
    }

    public Double getDouble( String key )
    {
        return getDouble( key, null );
    }

    public Double getDouble( String key, Double defaultValue )
    {
        String defaultString = null;
        String returnString = null;
        if ( defaultValue != null )
        {
            defaultString = defaultValue.toString();
        }
        returnString = getString( key, defaultString );
        if ( returnString != null )
        {
            return new Double( returnString );
        }
        return null;
    }

    public Boolean getBoolean( String key )
    {
        return getBoolean( key, null );
    }

    public Boolean getBoolean( String key, Boolean defaultValue )
    {
        String defaultString = null;
        String returnString = null;
        if ( defaultValue != null )
        {
            defaultString = defaultValue.toString();
        }
        returnString = getString( key, defaultString );
        if ( returnString != null )
        {
            return new Boolean( returnString );
        }
        return null;
    }

    public void printFileTrace()
    {
        System.out.println( "Properties Files Loaded: " );
        for ( String propertyFileName : propertySources.keySet() )
        {
            System.out.print( "  " );
            System.out.println( ": " + propertyFileName );
        }
    }

    /**
     * Returns which property file has the given property in it.
     */
    public String getPropFile( String key )
    {
        for ( String propFileKey : propertySources.keySet() )
        {
            Properties props = propertySources.get( propFileKey );
            String tempVal = props.getProperty( key );
            if ( tempVal != null )
            {
                return propFileKey;
            }
        }

        return null;
    }

    /**
     * Find all property keys with the given prefix. Note that this is a case sensitive comparison, but usually
     * properties keys are all lower case.
     * 
     * @param prefix
     *            The prefix to find the properties for.
     * 
     * @return List of property names which match.
     */
    public List<String> findPropertyKeysByPrefix( String prefix )
    {
        List<String> propKeys = new ArrayList<String>();

        for ( Properties prop : propertySources.values() )
        {
            for ( Object o : prop.keySet() )
            {
                try
                {
                    String propName = (String) o;
                    if ( propName.startsWith( prefix ) )
                    {
                        propKeys.add( propName );
                    }
                }
                catch ( ClassCastException e )
                {
                    // Do nothing, should never happen, but if it does, we don't care,
                    // just skip the offending key.
                }
            }
        }

        return propKeys;
    }

    public void setString( String key, String value )
    {
        runtimeProperties.put( key, value );
    }

    // ----------
    // private
    // ----------

    private void findAndLoadPropertyFile( String filename )
    {
        Properties newProp = null;
        FileInputStream inFile = null;
        InputStream inStream = null;

        //
        // Check for any files that may have been specified from the command-line via a -D.
        //
        String filenameProp = System.getProperty( filename );
        if ( filenameProp != null )
        {
            try
            {
                inFile = new FileInputStream( filenameProp );
                if ( inFile != null )
                {
                    newProp = new Properties();
                    newProp.load( inFile );
                    addProperties( "-D" + filename + "=" + filenameProp, newProp );
                }
            }
            catch ( IOException ioex )
            {
                // Ignore.
            }
        }

        //
        // Check the classpath for files.
        //
        try
        {
            inStream = getClass().getResourceAsStream( "/" + filename );
            if ( inStream != null )
            {
                newProp = new Properties();
                newProp.load( inStream );
                propertySources.put( new String( ( getClass().getResource( "/" + filename ) ).toString() ), newProp );
            }
        }
        catch ( IOException ioex )
        {
            // Ignore.
        }

        //
        // Check for file in the current directory.
        //
        try
        {
            inFile = new FileInputStream( filename );
            if ( inFile != null )
            {
                newProp = new Properties();
                newProp.load( inFile );
                propertySources.put( new String( "." + filename ), newProp );
            }
        }
        catch ( IOException ioex )
        {
            // Ignore.
        }
    }
}
